/*
 * bus.cpp
 *
 *  Created on: 01/07/2014
 *      Author: nicolas
 */

#include "../comun/SalaDePersonas.h"
#include "../comun/SalaRemota.h"

#include "../comun/iSemaforo.h"
#include "../ipc/Semaforo.h"
#include "../ipcRemoto/SemaforoRemoto.h"

#include "../comun/Parque.h"
#include "../comun/ParqueRemoto.h"

#include "../interfaces/iPersonaBUS.h"
#include "../interfaces/iSistemaBUS.h"
#include <stdlib.h>
#include <unistd.h>
#include <vector>

#include <syslog.h>
#include <stdio.h>

	/*
	 * Supongo que el bus empieza en una sala
	 *
	 * pasajeros = 0
	 * repetir:
	 * 		tomar shmem de la sala actual
	 * 		ver si hay gente
	 * 		si no hay gente & pasajeros = 0:
	 * 			tomar shmem otra sala
	 * 			ver si hay gente
	 * 			liberar shmem otra sala
	 * 			si no hay gente:
	 * 				marcar esperando en sala shmem sala actual
	 * 				liberar shmem sala actual
	 * 				wait semaforo bus
	 * 				continue desde repetir
	 * 			si hay gente:
	 * 				liberar shmem sala actual
	 * 				viajar a otra sala
	 * 				continue desde repetir
	 * 		si no hay gente & pasajeros > 0:
	 * 			viajar a otra sala
	 * 			repetir por cada pasajero
	 * 				enviar mensaje a pasajero para que baje
	 * 				recibir mensaje que pasajero que bajo
	 * 				continue desde repetir
	 * 		si hay gente:
	 * 			liberar shmem de la sala actual
	 * 			mandar mensaje a sala de que deje entrar persona
	 * 			recibir mensaje de que subio persona, registrar id
	 * 			continue desde repetir
	 *
	 */




#define TIEMPO_DE_VIAJE		6

/**
 *
 *  arg[1] numero de BUS
 *
 */


void intercambiar(iSemaforo* &a, iSemaforo* &b) {
	iSemaforo* aux;
	aux = a;
	a = b;
	b = aux;
}

void intercambiar(iSalaBUS* &a, iSalaBUS* &b) {
	iSalaBUS *aux;
	aux = a;
	a = b;
	b = aux;
}


int reemplazarBUS(Tipos::LugarDeSala& lugarDestino, std::vector<int>& personas) {
	iSistemaBUS sist;
	int numBusReemplazado;

	personas.clear();

	numBusReemplazado = sist.esperarPedidoDeRepuesto(lugarDestino);

	int idPersona;

	do {

		idPersona = sist.esperarSubidaDePersona(numBusReemplazado);

		if (idPersona != 0) {
			personas.push_back(idPersona);
		}

	} while (idPersona != 0);

	return numBusReemplazado;
}


bool roturaDeBus() {

	int valor = rand() % 10;

	if (valor == 5) {
		syslog(LOG_INFO, "ROTURA DE BUS");
		return true;
	}
	else {
		return false;
	}
}


/**
 * Retorna true si se rompio el bus
 */
bool viajarBus(int numBus, Tipos::LugarDeSala destino, std::vector<int>& pasajeros, iSemaforo* mtxParque, Parque& parque) {
	iSistemaBUS sist;

	if (roturaDeBus()) {

		syslog(LOG_INFO, "El BUS se rompio.");

		syslog(LOG_INFO, "TOMANDO MUTEX del PARQUE.(Por rotura de Bus)");
		mtxParque->wait();
		bool hayRepuestos = (parque.busesDeRepuesto() > 0);
		mtxParque->signal();
		syslog(LOG_INFO, "LIBERANDO MUTEX del PARQUE.(Por rotura de Bus)");

		if (hayRepuestos) {
			// envio peticion de repuesto
			sist.peticionDeBusDeRepuesto(numBus, destino);

			for (int i = 0; (unsigned)i < pasajeros.size(); ++i) {
				sist.esperarPeticionYEnviarPersona(pasajeros[i], numBus);
			}

			// finalizacion de pasajeros
			sist.esperarPeticionYEnviarPersona(0, numBus);
		}

		syslog(LOG_INFO, "Finaliza BUS por rotura");
		//exit(EXIT_SUCCESS);
		return true;

	}
	else {
		if (destino == Tipos::LUGAR_ZOO) {
			syslog(LOG_INFO,"BUS viajando a la Sala del ZOO");
		}
		else if (destino == Tipos::LUGAR_ENTRADA) {
			syslog(LOG_INFO, "BUS viajando a la Sala de la ENTRADA");
		}
		else {
			syslog(LOG_ERR, "BUS llendo a un destino ERRONEO");
		}
		sleep(TIEMPO_DE_VIAJE);

		return false;
	}

}
/**
 * Retorna true si se rompio el bus
 */
bool comprobarSalas(int numBUS, iSalaBUS*& salaActual,iSemaforo*& mActual, iSalaBUS*& salaDistante, iSemaforo*& mDistante,
		iSemaforo* semBUSParado, iSemaforo* mtxParque, Parque& parque, std::vector<int>& personas) {

	bool hayPersonas;

	mActual->wait();
	hayPersonas = salaActual->hayPersonas();
	mActual->signal();

	if (hayPersonas == false) { // Si no hay personas en la sala actual
		mDistante->wait();
		hayPersonas = salaDistante->hayPersonas();
		mDistante->signal();

		if (hayPersonas == false) { // Si no hay personas en la sala distante

			mActual->wait();
			salaActual->BUSDormido();
			mActual->signal();

			syslog(LOG_INFO, "Bus por dormir");
			semBUSParado->wait();
			syslog(LOG_INFO, "Bus se desperto");

			mActual->wait();
			hayPersonas = salaActual->hayPersonas();
			mActual->signal();

			if (hayPersonas == false) {

				// REALIZAR VIAJE
				//sleep(TIEMPO_DE_VIAJE);
				if (viajarBus(numBUS, salaDistante->lugar(), personas, mtxParque, parque)){
					return true;
				}
				intercambiar(salaActual, salaDistante);
				intercambiar(mActual, mDistante);

			}

		}
		else {
			// REALIZAR VIAJE
			//sleep(TIEMPO_DE_VIAJE);
			if(viajarBus(numBUS, salaDistante->lugar(), personas, mtxParque, parque)){
				return true;
			}
			intercambiar(salaActual, salaDistante);
			intercambiar(mActual, mDistante);
		}
	}

	return false;
}

void subirPasajeros(iSalaBUS* salaActual, iSemaforo* mActual, std::vector<int>& personas, iPersonaBUS& ipersona) {
	int idPersona;

	personas.clear();

	// mientras BUS no este lleno
	mActual->wait();
	while (personas.size() < CANT_MAX_BUS && salaActual->hayPersonas())  {
		salaActual->BUSsubiendoPersonas();
		mActual->signal();

		idPersona = ipersona.subirPersona(salaActual->numeroDeSala(), salaActual->lugar());

		personas.push_back(idPersona);

		mActual->wait();
	}
	salaActual->BUSDespierto();
	mActual->signal();

}

int main(int argc, char **argv) {

	int numBUS = -1;
	Tipos::LugarDeSala lugarDestinoDeReemplazo;
	std::vector<int> personas;
	bool reemplazo = false;

	char encabezado[80];

	if (argc == 1) {
		numBUS = reemplazarBUS(lugarDestinoDeReemplazo, personas);
		reemplazo = true;

		sprintf(encabezado, "BUS %i (Repuesto)", numBUS);
		openlog(encabezado, LOG_PID | LOG_CONS, LOG_LOCAL0);

		syslog(LOG_INFO, "Se Reemplazo el Bus %i roto", numBUS);

	} else if (argc == 2){
		numBUS = atoi(argv[1]);

		sprintf(encabezado, "BUS %i", numBUS);
		openlog(encabezado, LOG_PID | LOG_CONS, LOG_LOCAL0);

	}
	else {
		perror("Se debe ingresar N°BUs como argumento o sin args para que sea de Repuesto");
		return EXIT_FAILURE;
	}

	int numSala = numBUS;

	iPersonaBUS ipersona(numBUS);


	iSalaBUS *salaActual, *salaDistante;
	iSemaforo *mtxSalaActual, *mtxSalaDistante;


	SalaRemota salaEntrada(numSala, Tipos::LUGAR_ENTRADA,ipersona.idBus(), ipersona.idBroker());
	SalaRemota salaZoo(numSala, Tipos::LUGAR_ZOO, ipersona.idBus(), ipersona.idBroker());


	ParqueRemoto parque(ipersona.idBus(), ipersona.idBroker());

	iSemaforo *mtxParque = new SemaforoRemoto(ID_MUTEX_PARQUE, ipersona.idBus(), ipersona.idBroker()); //(RUTA, 1, ID_MUTEX_PARQUE);
	iSemaforo *mtxSalaEntrada = new SemaforoRemoto(ID_MUTEX_SALA_ENT(numSala),  ipersona.idBus(), ipersona.idBroker());//(RUTA, 1, ID_MUTEX_SALA_ENT(numSala));
	iSemaforo *mtxSalaZoo = new SemaforoRemoto(ID_MUTEX_SALA_ZOO(numSala), ipersona.idBus(), ipersona.idBroker());//(RUTA, 1, ID_MUTEX_SALA_ZOO(numSala));

	iSemaforo *semBUSParado = new SemaforoRemoto(ID_SEM_BUS(numBUS), ipersona.idBus(), ipersona.idBroker());//(RUTA, 1, ID_SEM_BUS(numBUS));

	salaActual = &salaEntrada;
	mtxSalaActual = mtxSalaEntrada;

	salaDistante = &salaZoo;
	mtxSalaDistante = mtxSalaZoo;


	if (reemplazo) {
		if (lugarDestinoDeReemplazo == Tipos::LUGAR_ENTRADA ) {
			intercambiar(salaActual, salaDistante);
			intercambiar(mtxSalaActual, mtxSalaDistante);
		}

		syslog(LOG_INFO, "TOMANDO MUTEX del PARQUE(luego del reemplazo).");
		mtxParque->wait();
		parque.repuestoDeBUSUsado();
		mtxParque->signal();
		syslog(LOG_INFO, "LIBERANDO MUTEX del PARQUE (luego del reemplazo).");
	}

	//std::vector<int> personas;

	bool parqueAbierto = true;

//	bool hayPersonas;

	while (parqueAbierto) {

		if (reemplazo == false) {

			if (comprobarSalas(numBUS,
					salaActual, mtxSalaActual, salaDistante, mtxSalaDistante,
					semBUSParado, mtxParque, parque, personas)) {
				return 0;
			}

			subirPasajeros(salaActual,mtxSalaActual, personas, ipersona);
		}
		else {
			reemplazo = false;
			//personas = personas;
		}

		// REALIZAR VIAJE
		//sleep(TIEMPO_DE_VIAJE);
		if (viajarBus(numBUS, salaDistante->lugar(), personas, mtxParque, parque)) {
			return 0;
		}
		intercambiar(salaActual, salaDistante);
		intercambiar(mtxSalaActual, mtxSalaDistante);


		// Descendiendo pasajeros
		if (personas.size() > 0) {
			mtxSalaActual->wait();
			salaActual->BUSbajandoPersonas();
			mtxSalaActual->signal();
		}

		int i = 0;
		while ((unsigned)i < personas.size())  {
			ipersona.descenderPersona(personas[i]);
			++i;
		}

		personas.clear();


		mtxSalaActual->wait();
		salaActual->BUSDespierto();
		mtxSalaActual->signal();
	}


	delete mtxParque;
	delete mtxSalaEntrada;
	delete mtxSalaZoo;
	delete semBUSParado;


	return 0;
}



